/*
 *   This program is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

/*
 *    ConjunctiveRule.java
 *    Copyright (C) 2001 University of Waikato, Hamilton, New Zealand
 *
 */

package weka.classifiers.rules;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Random;
import java.util.Vector;

import weka.classifiers.AbstractClassifier;
import weka.core.Attribute;
import weka.core.Capabilities;
import weka.core.Capabilities.Capability;
import weka.core.ContingencyTables;
import weka.core.Instance;
import weka.core.Instances;
import weka.core.Option;
import weka.core.OptionHandler;
import weka.core.RevisionHandler;
import weka.core.RevisionUtils;
import weka.core.Utils;
import weka.core.WeightedInstancesHandler;

/**
 * <!-- globalinfo-start --> This class implements a single conjunctive rule
 * learner that can predict for numeric and nominal class labels.<br/>
 * <br/>
 * A rule consists of antecedents "AND"ed together and the consequent (class
 * value) for the classification/regression. In this case, the consequent is the
 * distribution of the available classes (or mean for a numeric value) in the
 * dataset. If the test instance is not covered by this rule, then it's
 * predicted using the default class distributions/value of the data not covered
 * by the rule in the training data.This learner selects an antecedent by
 * computing the Information Gain of each antecendent and prunes the generated
 * rule using Reduced Error Prunning (REP) or simple pre-pruning based on the
 * number of antecedents.<br/>
 * <br/>
 * For classification, the Information of one antecedent is the weighted average
 * of the entropies of both the data covered and not covered by the rule.<br/>
 * For regression, the Information is the weighted average of the mean-squared
 * errors of both the data covered and not covered by the rule.<br/>
 * <br/>
 * In pruning, weighted average of the accuracy rates on the pruning data is
 * used for classification while the weighted average of the mean-squared errors
 * on the pruning data is used for regression.<br/>
 * <br/>
 * <p/>
 * <!-- globalinfo-end -->
 * 
 * <!-- options-start --> Valid options are:
 * <p/>
 * 
 * <pre>
 * -N &lt;number of folds&gt;
 *  Set number of folds for REP
 *  One fold is used as pruning set.
 *  (default 3)
 * </pre>
 * 
 * <pre>
 * -R
 *  Set if NOT uses randomization
 *  (default:use randomization)
 * </pre>
 * 
 * <pre>
 * -E
 *  Set whether consider the exclusive
 *  expressions for nominal attributes
 *  (default false)
 * </pre>
 * 
 * <pre>
 * -M &lt;min. weights&gt;
 *  Set the minimal weights of instances
 *  within a split.
 *  (default 2.0)
 * </pre>
 * 
 * <pre>
 * -P &lt;number of antecedents&gt;
 *  Set number of antecedents for pre-pruning
 *  if -1, then REP is used
 *  (default -1)
 * </pre>
 * 
 * <pre>
 * -S &lt;seed&gt;
 *  Set the seed of randomization
 *  (default 1)
 * </pre>
 * 
 * <!-- options-end -->
 * 
 * @author Xin XU (xx5@cs.waikato.ac.nz)
 * @version $Revision$
 */
public class ConjunctiveRule extends AbstractClassifier implements
  OptionHandler, WeightedInstancesHandler {

  /** for serialization */
  static final long serialVersionUID = -5938309903225087198L;

  /** The number of folds to split data into Grow and Prune for REP */
  private int m_Folds = 3;

  /** The class attribute of the data */
  private Attribute m_ClassAttribute;

  /** The vector of antecedents of this rule */
  protected ArrayList<Antd> m_Antds = null;

  /** The default rule distribution of the data not covered */
  protected double[] m_DefDstr = null;

  /** The consequent of this rule */
  protected double[] m_Cnsqt = null;

  /** Number of classes in the training data */
  private int m_NumClasses = 0;

  /** The seed to perform randomization */
  private long m_Seed = 1;

  /** The Random object used for randomization */
  private Random m_Random = null;

  /** The predicted classes recorded for each antecedent in the growing data */
  private ArrayList<double[][]> m_Targets;

  /** Whether to use exlusive expressions for nominal attributes */
  private boolean m_IsExclude = false;

  /** The minimal number of instance weights within a split */
  private double m_MinNo = 2.0;

  /** The number of antecedents in pre-pruning */
  private int m_NumAntds = -1;

  /**
   * Returns a string describing classifier
   * 
   * @return a description suitable for displaying in the explorer/experimenter
   *         gui
   */
  public String globalInfo() {

    return "This class implements a single conjunctive rule learner that can predict "
      + "for numeric and nominal class labels.\n\n"
      + "A rule consists of antecedents \"AND\"ed together and the consequent (class value) "
      + "for the classification/regression.  In this case, the consequent is the "
      + "distribution of the available classes (or mean for a numeric value) in the dataset. "
      + "If the test instance is not covered by this rule, then it's predicted "
      + "using the default class distributions/value of the data not covered by the "
      + "rule in the training data."
      + "This learner selects an antecedent by computing the Information Gain of each "
      + "antecendent and prunes the generated rule using Reduced Error Prunning (REP) "
      + "or simple pre-pruning based on the number of antecedents.\n\n"
      + "For classification, the Information of one antecedent is the weighted average of "
      + "the entropies of both the data covered and not covered by the rule.\n"
      + "For regression, the Information is the weighted average of the mean-squared errors "
      + "of both the data covered and not covered by the rule.\n\n"
      + "In pruning, weighted average of the accuracy rates on the pruning data is used "
      + "for classification while the weighted average of the mean-squared errors "
      + "on the pruning data is used for regression.\n\n";
  }

  /**
   * The single antecedent in the rule, which is composed of an attribute and
   * the corresponding value. There are two inherited classes, namely
   * NumericAntd and NominalAntd in which the attributes are numeric and nominal
   * respectively.
   */
  private abstract class Antd implements Serializable, RevisionHandler {

    /** for serialization */
    private static final long serialVersionUID = -8729076306737827571L;

    /** The attribute of the antecedent */
    protected Attribute att;

    /**
     * The attribute value of the antecedent. For numeric attribute, value is
     * either 0(1st bag) or 1(2nd bag)
     */
    protected double value;

    /** The maximum infoGain achieved by this antecedent test */
    protected double maxInfoGain;

    /** The information of this antecedent test on the growing data */
    protected double inform;

    /**
     * The parameter related to the meanSquaredError of the data not covered by
     * the previous antecedents when the class is numeric
     */
    protected double uncoverWtSq, uncoverWtVl, uncoverSum;

    /**
     * The parameters related to the data not covered by the previous
     * antecedents when the class is nominal
     */
    protected double[] uncover;

    /** Constructor for nominal class */
    public Antd(Attribute a, double[] unc) {
      att = a;
      value = Double.NaN;
      maxInfoGain = 0;
      inform = Double.NaN;
      uncover = unc;
    }

    /**
     * Constructor for numeric class
     */
    public Antd(Attribute a, double uncoveredWtSq, double uncoveredWtVl,
      double uncoveredWts) {
      att = a;
      value = Double.NaN;
      maxInfoGain = 0;
      inform = Double.NaN;
      uncoverWtSq = uncoveredWtSq;
      uncoverWtVl = uncoveredWtVl;
      uncoverSum = uncoveredWts;
    }

    /* The abstract members for inheritance */
    public abstract Instances[] splitData(Instances data, double defInfo);

    public abstract boolean isCover(Instance inst);

    @Override
    public abstract String toString();

    /* Get functions of this antecedent */
    public Attribute getAttr() {
      return att;
    }

    public double getAttrValue() {
      return value;
    }

    public double getMaxInfoGain() {
      return maxInfoGain;
    }

    public double getInfo() {
      return inform;
    }

    /**
     * Function used to calculate the weighted mean squared error, i.e.,
     * sum[x-avg(x)]^2 based on the given elements of the formula:
     * meanSquaredError = sum(Wi*Xi^2) - (sum(WiXi))^2/sum(Wi)
     * 
     * @param weightedSq sum(Wi*Xi^2)
     * @param weightedValue sum(WiXi)
     * @param sum sum of weights
     * @return the weighted mean-squared error
     */
    protected double wtMeanSqErr(double weightedSq, double weightedValue,
      double sum) {
      if (Utils.smOrEq(sum, 1.0E-6)) {
        return 0;
      }
      return (weightedSq - (weightedValue * weightedValue) / sum);
    }

    /**
     * Function used to calculate the entropy of given vector of values entropy
     * = (1/sum)*{-sigma[i=1..P](Xi*log2(Xi)) + sum*log2(sum)} where P is the
     * length of the vector
     * 
     * @param value the given vector of values
     * @param sum the sum of the given values. It's provided just for
     *          efficiency.
     * @return the entropy
     */
    protected double entropy(double[] value, double sum) {
      if (Utils.smOrEq(sum, 1.0E-6)) {
        return 0;
      }

      double entropy = 0;
      for (int i = 0; i < value.length; i++) {
        if (!Utils.eq(value[i], 0)) {
          entropy -= value[i] * Utils.log2(value[i]);
        }
      }
      entropy += sum * Utils.log2(sum);
      entropy /= sum;
      return entropy;
    }

    /**
     * Returns the revision string.
     * 
     * @return the revision
     */
    @Override
    public String getRevision() {
      return RevisionUtils.extract("$Revision$");
    }
  }

  /**
   * The antecedent with numeric attribute
   */
  private class NumericAntd extends Antd {

    /** for serialization */
    static final long serialVersionUID = -7957266498918210436L;

    /** The split point for this numeric antecedent */
    private double splitPoint;

    /**
     * Constructor for nominal class
     */
    public NumericAntd(Attribute a, double[] unc) {
      super(a, unc);
      splitPoint = Double.NaN;
    }

    /**
     * Constructor for numeric class
     */
    public NumericAntd(Attribute a, double sq, double vl, double wts) {
      super(a, sq, vl, wts);
      splitPoint = Double.NaN;
    }

    /**
     * Implements the splitData function. This procedure is to split the data
     * into two bags according to the information gain of the numeric attribute
     * value the data with missing values are stored in the last split. The
     * maximum infoGain is also calculated.
     * 
     * @param insts the data to be split
     * @param defInfo the default information for data
     * @return the array of data after split
     */
    @Override
    public Instances[] splitData(Instances insts, double defInfo) {
      Instances data = new Instances(insts);
      data.sort(att);
      int total = data.numInstances();// Total number of instances without
      // missing value for att
      maxInfoGain = 0;
      value = 0;

      // Compute minimum number of Instances required in each split
      double minSplit;
      if (m_ClassAttribute.isNominal()) {
        minSplit = 0.1 * (data.sumOfWeights()) / (m_ClassAttribute.numValues());
        if (Utils.smOrEq(minSplit, m_MinNo)) {
          minSplit = m_MinNo;
        } else if (Utils.gr(minSplit, 25)) {
          minSplit = 25;
        }
      } else {
        minSplit = m_MinNo;
      }

      double[] fst = null, snd = null, missing = null;
      if (m_ClassAttribute.isNominal()) {
        fst = new double[m_NumClasses];
        snd = new double[m_NumClasses];
        missing = new double[m_NumClasses];

        for (int v = 0; v < m_NumClasses; v++) {
          fst[v] = snd[v] = missing[v] = 0.0;
        }
      }
      double fstCover = 0, sndCover = 0, fstWtSq = 0, sndWtSq = 0, fstWtVl = 0, sndWtVl = 0;

      int split = 1; // Current split position
      int prev = 0; // Previous split position
      int finalSplit = split; // Final split position

      for (int x = 0; x < data.numInstances(); x++) {
        Instance inst = data.instance(x);
        if (inst.isMissing(att)) {
          total = x;
          break;
        }

        sndCover += inst.weight();
        if (m_ClassAttribute.isNominal()) {
          snd[(int) inst.classValue()] += inst.weight();
        } else { // Numeric class
          sndWtSq += inst.weight() * inst.classValue() * inst.classValue();
          sndWtVl += inst.weight() * inst.classValue();
        }
      }

      // Enough Instances with known values?
      if (Utils.sm(sndCover, (2 * minSplit))) {
        return null;
      }

      double msingWtSq = 0, msingWtVl = 0;
      Instances missingData = new Instances(data, 0);
      for (int y = total; y < data.numInstances(); y++) {
        Instance inst = data.instance(y);
        missingData.add(inst);
        if (m_ClassAttribute.isNominal()) {
          missing[(int) inst.classValue()] += inst.weight();
        } else {
          msingWtSq += inst.weight() * inst.classValue() * inst.classValue();
          msingWtVl += inst.weight() * inst.classValue();
        }
      }

      if (total == 0) {
        return null; // Data all missing for the attribute
      }

      splitPoint = data.instance(total - 1).value(att);

      for (; split < total; split++) {
        if (!Utils.eq(data.instance(split).value(att), // Can't split
          data.instance(prev).value(att))) {// within same value

          // Move the split point
          for (int y = prev; y < split; y++) {
            Instance inst = data.instance(y);
            fstCover += inst.weight();
            sndCover -= inst.weight();
            if (m_ClassAttribute.isNominal()) { // Nominal class
              fst[(int) inst.classValue()] += inst.weight();
              snd[(int) inst.classValue()] -= inst.weight();
            } else { // Numeric class
              fstWtSq += inst.weight() * inst.classValue() * inst.classValue();
              fstWtVl += inst.weight() * inst.classValue();
              sndWtSq -= inst.weight() * inst.classValue() * inst.classValue();
              sndWtVl -= inst.weight() * inst.classValue();
            }
          }

          if (Utils.sm(fstCover, minSplit) || Utils.sm(sndCover, minSplit)) {
            prev = split; // Cannot split because either
            continue; // split has not enough data
          }

          double fstEntp = 0, sndEntp = 0;

          if (m_ClassAttribute.isNominal()) {
            fstEntp = entropy(fst, fstCover);
            sndEntp = entropy(snd, sndCover);
          } else {
            fstEntp = wtMeanSqErr(fstWtSq, fstWtVl, fstCover) / fstCover;
            sndEntp = wtMeanSqErr(sndWtSq, sndWtVl, sndCover) / sndCover;
          }

          /* Which bag has higher information gain? */
          boolean isFirst;
          double fstInfoGain, sndInfoGain;
          double info, infoGain, fstInfo, sndInfo;
          if (m_ClassAttribute.isNominal()) {
            double sum = data.sumOfWeights();
            double otherCover, whole = sum + Utils.sum(uncover), otherEntropy;
            double[] other = null;

            // InfoGain of first bag
            other = new double[m_NumClasses];
            for (int z = 0; z < m_NumClasses; z++) {
              other[z] = uncover[z] + snd[z] + missing[z];
            }
            otherCover = whole - fstCover;
            otherEntropy = entropy(other, otherCover);
            // Weighted average
            fstInfo = (fstEntp * fstCover + otherEntropy * otherCover) / whole;
            fstInfoGain = defInfo - fstInfo;

            // InfoGain of second bag
            other = new double[m_NumClasses];
            for (int z = 0; z < m_NumClasses; z++) {
              other[z] = uncover[z] + fst[z] + missing[z];
            }
            otherCover = whole - sndCover;
            otherEntropy = entropy(other, otherCover);
            // Weighted average
            sndInfo = (sndEntp * sndCover + otherEntropy * otherCover) / whole;
            sndInfoGain = defInfo - sndInfo;
          } else {
            double sum = data.sumOfWeights();
            double otherWtSq = (sndWtSq + msingWtSq + uncoverWtSq), otherWtVl = (sndWtVl
              + msingWtVl + uncoverWtVl), otherCover = (sum - fstCover + uncoverSum);

            fstInfo = Utils.eq(fstCover, 0) ? 0 : (fstEntp * fstCover);
            fstInfo += wtMeanSqErr(otherWtSq, otherWtVl, otherCover);
            fstInfoGain = defInfo - fstInfo;

            otherWtSq = (fstWtSq + msingWtSq + uncoverWtSq);
            otherWtVl = (fstWtVl + msingWtVl + uncoverWtVl);
            otherCover = sum - sndCover + uncoverSum;
            sndInfo = Utils.eq(sndCover, 0) ? 0 : (sndEntp * sndCover);
            sndInfo += wtMeanSqErr(otherWtSq, otherWtVl, otherCover);
            sndInfoGain = defInfo - sndInfo;
          }

          if (Utils.gr(fstInfoGain, sndInfoGain)
            || (Utils.eq(fstInfoGain, sndInfoGain) && (Utils.sm(fstEntp,
              sndEntp)))) {
            isFirst = true;
            infoGain = fstInfoGain;
            info = fstInfo;
          } else {
            isFirst = false;
            infoGain = sndInfoGain;
            info = sndInfo;
          }

          boolean isUpdate = Utils.gr(infoGain, maxInfoGain);

          /* Check whether so far the max infoGain */
          if (isUpdate) {
            splitPoint = ((data.instance(split).value(att)) + (data
              .instance(prev).value(att))) / 2.0;
            value = ((isFirst) ? 0 : 1);
            inform = info;
            maxInfoGain = infoGain;
            finalSplit = split;
          }
          prev = split;
        }
      }

      /* Split the data */
      Instances[] splitData = new Instances[3];
      splitData[0] = new Instances(data, 0, finalSplit);
      splitData[1] = new Instances(data, finalSplit, total - finalSplit);
      splitData[2] = new Instances(missingData);

      return splitData;
    }

    /**
     * Whether the instance is covered by this antecedent
     * 
     * @param inst the instance in question
     * @return the boolean value indicating whether the instance is covered by
     *         this antecedent
     */
    @Override
    public boolean isCover(Instance inst) {
      boolean isCover = false;
      if (!inst.isMissing(att)) {
        if (Utils.eq(value, 0)) {
          if (Utils.smOrEq(inst.value(att), splitPoint)) {
            isCover = true;
          }
        } else if (Utils.gr(inst.value(att), splitPoint)) {
          isCover = true;
        }
      }
      return isCover;
    }

    /**
     * Prints this antecedent
     * 
     * @return a textual description of this antecedent
     */
    @Override
    public String toString() {
      String symbol = Utils.eq(value, 0.0) ? " <= " : " > ";
      return (att.name() + symbol + Utils.doubleToString(splitPoint, 6));
    }

    /**
     * Returns the revision string.
     * 
     * @return the revision
     */
    @Override
    public String getRevision() {
      return RevisionUtils.extract("$Revision$");
    }
  }

  /**
   * The antecedent with nominal attribute
   */
  class NominalAntd extends Antd {

    /** for serialization */
    static final long serialVersionUID = -5949864163376447424L;

    /* The parameters of infoGain calculated for each attribute value */
    private final double[][] stats;
    private final double[] coverage;
    private boolean isIn;

    /**
     * Constructor for nominal class
     */
    public NominalAntd(Attribute a, double[] unc) {
      super(a, unc);
      int bag = att.numValues();
      stats = new double[bag][m_NumClasses];
      coverage = new double[bag];
      isIn = true;
    }

    /**
     * Constructor for numeric class
     */
    public NominalAntd(Attribute a, double sq, double vl, double wts) {
      super(a, sq, vl, wts);
      int bag = att.numValues();
      stats = null;
      coverage = new double[bag];
      isIn = true;
    }

    /**
     * Implements the splitData function. This procedure is to split the data
     * into bags according to the nominal attribute value the data with missing
     * values are stored in the last bag. The infoGain for each bag is also
     * calculated.
     * 
     * @param data the data to be split
     * @param defInfo the default information for data
     * @return the array of data after split
     */
    @Override
    public Instances[] splitData(Instances data, double defInfo) {
      int bag = att.numValues();
      Instances[] splitData = new Instances[bag + 1];
      double[] wSq = new double[bag];
      double[] wVl = new double[bag];
      double totalWS = 0, totalWV = 0, msingWS = 0, msingWV = 0, sum = data
        .sumOfWeights();
      double[] all = new double[m_NumClasses];
      double[] missing = new double[m_NumClasses];

      for (int w = 0; w < m_NumClasses; w++) {
        all[w] = missing[w] = 0;
      }

      for (int x = 0; x < bag; x++) {
        coverage[x] = wSq[x] = wVl[x] = 0;
        if (stats != null) {
          for (int y = 0; y < m_NumClasses; y++) {
            stats[x][y] = 0;
          }
        }
        splitData[x] = new Instances(data, data.numInstances());
      }
      splitData[bag] = new Instances(data, data.numInstances());

      // Record the statistics of data
      for (int x = 0; x < data.numInstances(); x++) {
        Instance inst = data.instance(x);
        if (!inst.isMissing(att)) {
          int v = (int) inst.value(att);
          splitData[v].add(inst);
          coverage[v] += inst.weight();
          if (m_ClassAttribute.isNominal()) { // Nominal class
            stats[v][(int) inst.classValue()] += inst.weight();
            all[(int) inst.classValue()] += inst.weight();
          } else { // Numeric class
            wSq[v] += inst.weight() * inst.classValue() * inst.classValue();
            wVl[v] += inst.weight() * inst.classValue();
            totalWS += inst.weight() * inst.classValue() * inst.classValue();
            totalWV += inst.weight() * inst.classValue();
          }
        } else {
          splitData[bag].add(inst);
          if (m_ClassAttribute.isNominal()) { // Nominal class
            all[(int) inst.classValue()] += inst.weight();
            missing[(int) inst.classValue()] += inst.weight();
          } else { // Numeric class
            totalWS += inst.weight() * inst.classValue() * inst.classValue();
            totalWV += inst.weight() * inst.classValue();
            msingWS += inst.weight() * inst.classValue() * inst.classValue();
            msingWV += inst.weight() * inst.classValue();
          }
        }
      }

      // The total weights of the whole grow data
      double whole;
      if (m_ClassAttribute.isNominal()) {
        whole = sum + Utils.sum(uncover);
      } else {
        whole = sum + uncoverSum;
      }

      // Find the split
      double minEntrp = Double.MAX_VALUE;
      maxInfoGain = 0;

      // Check if >=2 splits have more than the minimal data
      int count = 0;
      for (int x = 0; x < bag; x++) {
        if (Utils.grOrEq(coverage[x], m_MinNo)) {
          ++count;
        }
      }

      if (count < 2) { // Don't split
        maxInfoGain = 0;
        inform = defInfo;
        value = Double.NaN;
        return null;
      }

      for (int x = 0; x < bag; x++) {
        double t = coverage[x], entrp, infoGain;

        if (Utils.sm(t, m_MinNo)) {
          continue;
        }

        if (m_ClassAttribute.isNominal()) { // Nominal class
          double[] other = new double[m_NumClasses];
          for (int y = 0; y < m_NumClasses; y++) {
            other[y] = all[y] - stats[x][y] + uncover[y];
          }
          double otherCover = whole - t;

          // Entropies of data covered and uncovered
          entrp = entropy(stats[x], t);
          double uncEntp = entropy(other, otherCover);

          if (m_Debug) {
            System.err.println(defInfo + " " + entrp + " " + t + " " + uncEntp
              + " " + otherCover + " " + whole);
          }

          // Weighted average
          infoGain = defInfo - (entrp * t + uncEntp * otherCover) / whole;
        } else { // Numeric class
          double weight = (whole - t);
          entrp = wtMeanSqErr(wSq[x], wVl[x], t) / t;
          infoGain = defInfo
            - (entrp * t)
            - wtMeanSqErr((totalWS - wSq[x] + uncoverWtSq),
              (totalWV - wVl[x] + uncoverWtVl), weight);
        }

        // Test the exclusive expression
        boolean isWithin = true;
        if (m_IsExclude) {
          double infoGain2, entrp2;
          if (m_ClassAttribute.isNominal()) { // Nominal class
            double[] other2 = new double[m_NumClasses];
            double[] notIn = new double[m_NumClasses];
            for (int y = 0; y < m_NumClasses; y++) {
              other2[y] = stats[x][y] + missing[y] + uncover[y];
              notIn[y] = all[y] - stats[x][y] - missing[y];
            }

            double msSum = Utils.sum(missing);
            double otherCover2 = t + msSum + Utils.sum(uncover);

            entrp2 = entropy(notIn, (sum - t - msSum));
            double uncEntp2 = entropy(other2, otherCover2);
            infoGain2 = defInfo
              - (entrp2 * (sum - t - msSum) + uncEntp2 * otherCover2) / whole;
          } else { // Numeric class
            double msWts = splitData[bag].sumOfWeights();
            double weight2 = t + uncoverSum + msWts;

            entrp2 = wtMeanSqErr((totalWS - wSq[x] - msingWS), (totalWV
              - wVl[x] - msingWV), (sum - t - msWts))
              / (sum - t - msWts);
            infoGain2 = defInfo
              - entrp2
              * (sum - t - msWts)
              - wtMeanSqErr((wSq[x] + uncoverWtSq + msingWS), (wVl[x]
                + uncoverWtVl + msingWV), weight2);
          }

          // Use the exclusive expression?
          if (Utils.gr(infoGain2, infoGain)
            || (Utils.eq(infoGain2, infoGain) && Utils.sm(entrp2, entrp))) {
            infoGain = infoGain2;
            entrp = entrp2;
            isWithin = false;
          }
        }

        // Test this split
        if (Utils.gr(infoGain, maxInfoGain)
          || (Utils.eq(infoGain, maxInfoGain) && Utils.sm(entrp, minEntrp))) {
          value = x;
          maxInfoGain = infoGain;
          inform = -(maxInfoGain - defInfo);
          minEntrp = entrp;
          isIn = isWithin;
        }
      }

      return splitData;
    }

    /**
     * Whether the instance is covered by this antecedent
     * 
     * @param inst the instance in question
     * @return the boolean value indicating whether the instance is covered by
     *         this antecedent
     */
    @Override
    public boolean isCover(Instance inst) {
      boolean isCover = false;
      if (!inst.isMissing(att)) {
        if (isIn) {
          if (Utils.eq(inst.value(att), value)) {
            isCover = true;
          }
        } else if (!Utils.eq(inst.value(att), value)) {
          isCover = true;
        }
      }
      return isCover;
    }

    /**
     * Whether the expression is "att = value" or att != value" for this nominal
     * attribute. True if in the former expression, otherwise the latter
     * 
     * @return the boolean value
     */
    public boolean isIn() {
      return isIn;
    }

    /**
     * Prints this antecedent
     * 
     * @return a textual description of this antecedent
     */
    @Override
    public String toString() {
      String symbol = isIn ? " = " : " != ";
      return (att.name() + symbol + att.value((int) value));
    }

    /**
     * Returns the revision string.
     * 
     * @return the revision
     */
    @Override
    public String getRevision() {
      return RevisionUtils.extract("$Revision$");
    }
  }

  /**
   * Returns an enumeration describing the available options Valid options are:
   * <p>
   * 
   * -N number <br>
   * Set number of folds for REP. One fold is used as the pruning set. (Default:
   * 3)
   * <p>
   * 
   * -R <br>
   * Set if NOT randomize the data before split to growing and pruning data. If
   * NOT set, the seed of randomization is specified by the -S option. (Default:
   * randomize)
   * <p>
   * 
   * -S <br>
   * Seed of randomization. (Default: 1)
   * <p>
   * 
   * -E <br>
   * Set whether consider the exclusive expressions for nominal attribute split.
   * (Default: false)
   * <p>
   * 
   * -M number <br>
   * Set the minimal weights of instances within a split. (Default: 2)
   * <p>
   * 
   * -P number <br>
   * Set the number of antecedents allowed in the rule if pre-pruning is used.
   * If this value is other than -1, then pre-pruning will be used, otherwise
   * the rule uses REP. (Default: -1)
   * <p>
   * 
   * @return an enumeration of all the available options
   */
  @Override
  public Enumeration<Option> listOptions() {

    Vector<Option> newVector = new Vector<Option>(6);

    newVector.addElement(new Option("\tSet number of folds for REP\n"
      + "\tOne fold is used as pruning set.\n" + "\t(default 3)", "N", 1,
      "-N <number of folds>"));

    newVector.addElement(new Option("\tSet if NOT uses randomization\n"
      + "\t(default:use randomization)", "R", 0, "-R"));

    newVector.addElement(new Option("\tSet whether consider the exclusive\n"
      + "\texpressions for nominal attributes\n" + "\t(default false)", "E", 0,
      "-E"));

    newVector
      .addElement(new Option("\tSet the minimal weights of instances\n"
        + "\twithin a split.\n" + "\t(default 2.0)", "M", 1,
        "-M <min. weights>"));

    newVector.addElement(new Option(
      "\tSet number of antecedents for pre-pruning\n"
        + "\tif -1, then REP is used\n" + "\t(default -1)", "P", 1,
      "-P <number of antecedents>"));

    newVector.addElement(new Option("\tSet the seed of randomization\n"
      + "\t(default 1)", "S", 1, "-S <seed>"));

    newVector.addAll(Collections.list(super.listOptions()));

    return newVector.elements();
  }

  /**
   * Parses a given list of options.
   * <p/>
   * 
   * <!-- options-start --> Valid options are:
   * <p/>
   * 
   * <pre>
   * -N &lt;number of folds&gt;
   *  Set number of folds for REP
   *  One fold is used as pruning set.
   *  (default 3)
   * </pre>
   * 
   * <pre>
   * -R
   *  Set if NOT uses randomization
   *  (default:use randomization)
   * </pre>
   * 
   * <pre>
   * -E
   *  Set whether consider the exclusive
   *  expressions for nominal attributes
   *  (default false)
   * </pre>
   * 
   * <pre>
   * -M &lt;min. weights&gt;
   *  Set the minimal weights of instances
   *  within a split.
   *  (default 2.0)
   * </pre>
   * 
   * <pre>
   * -P &lt;number of antecedents&gt;
   *  Set number of antecedents for pre-pruning
   *  if -1, then REP is used
   *  (default -1)
   * </pre>
   * 
   * <pre>
   * -S &lt;seed&gt;
   *  Set the seed of randomization
   *  (default 1)
   * </pre>
   * 
   * <!-- options-end -->
   * 
   * @param options the list of options as an array of strings
   * @throws Exception if an option is not supported
   */
  @Override
  public void setOptions(String[] options) throws Exception {

    String numFoldsString = Utils.getOption('N', options);
    if (numFoldsString.length() != 0) {
      m_Folds = Integer.parseInt(numFoldsString);
    } else {
      m_Folds = 3;
    }

    String minNoString = Utils.getOption('M', options);
    if (minNoString.length() != 0) {
      m_MinNo = Double.parseDouble(minNoString);
    } else {
      m_MinNo = 2.0;
    }

    String seedString = Utils.getOption('S', options);
    if (seedString.length() != 0) {
      m_Seed = Integer.parseInt(seedString);
    } else {
      m_Seed = 1;
    }

    String numAntdsString = Utils.getOption('P', options);
    if (numAntdsString.length() != 0) {
      m_NumAntds = Integer.parseInt(numAntdsString);
    } else {
      m_NumAntds = -1;
    }

    m_IsExclude = Utils.getFlag('E', options);

    super.setOptions(options);

    Utils.checkForRemainingOptions(options);
  }

  /**
   * Gets the current settings of the Classifier.
   * 
   * @return an array of strings suitable for passing to setOptions
   */
  @Override
  public String[] getOptions() {

    Vector<String> options = new Vector<String>();

    options.add("-N");
    options.add("" + m_Folds);
    options.add("-M");
    options.add("" + m_MinNo);
    options.add("-P");
    options.add("" + m_NumAntds);
    options.add("-S");
    options.add("" + m_Seed);

    if (m_IsExclude) {
      options.add("-E");
    }

    return options.toArray(new String[0]);
  }

  /** The access functions for parameters */

  /**
   * Returns the tip text for this property
   * 
   * @return tip text for this property suitable for displaying in the
   *         explorer/experimenter gui
   */
  public String foldsTipText() {
    return "Determines the amount of data used for pruning. One fold is used for "
      + "pruning, the rest for growing the rules.";
  }

  /**
   * the number of folds to use
   * 
   * @param folds the number of folds to use
   */
  public void setFolds(int folds) {
    m_Folds = folds;
  }

  /**
   * returns the current number of folds
   * 
   * @return the number of folds
   */
  public int getFolds() {
    return m_Folds;
  }

  /**
   * Returns the tip text for this property
   * 
   * @return tip text for this property suitable for displaying in the
   *         explorer/experimenter gui
   */
  public String seedTipText() {
    return "The seed used for randomizing the data.";
  }

  /**
   * sets the seed for randomizing the data
   * 
   * @param s the seed value
   */
  public void setSeed(long s) {
    m_Seed = s;
  }

  /**
   * returns the current seed value for randomizing the data
   * 
   * @return the seed value
   */
  public long getSeed() {
    return m_Seed;
  }

  /**
   * Returns the tip text for this property
   * 
   * @return tip text for this property suitable for displaying in the
   *         explorer/experimenter gui
   */
  public String exclusiveTipText() {
    return "Set whether to consider exclusive expressions for nominal "
      + "attribute splits.";
  }

  /**
   * Returns whether exclusive expressions for nominal attributes splits are
   * considered
   * 
   * @return true if exclusive expressions for nominal attributes splits are
   *         considered
   */
  public boolean getExclusive() {
    return m_IsExclude;
  }

  /**
   * Sets whether exclusive expressions for nominal attributes splits are
   * considered
   * 
   * @param e whether to consider exclusive expressions for nominal attribute
   *          splits
   */
  public void setExclusive(boolean e) {
    m_IsExclude = e;
  }

  /**
   * Returns the tip text for this property
   * 
   * @return tip text for this property suitable for displaying in the
   *         explorer/experimenter gui
   */
  public String minNoTipText() {
    return "The minimum total weight of the instances in a rule.";
  }

  /**
   * Sets the minimum total weight of the instances in a rule
   * 
   * @param m the minimum total weight of the instances in a rule
   */
  public void setMinNo(double m) {
    m_MinNo = m;
  }

  /**
   * Gets the minimum total weight of the instances in a rule
   * 
   * @return the minimum total weight of the instances in a rule
   */
  public double getMinNo() {
    return m_MinNo;
  }

  /**
   * Returns the tip text for this property
   * 
   * @return tip text for this property suitable for displaying in the
   *         explorer/experimenter gui
   */
  public String numAntdsTipText() {
    return "Set the number of antecedents allowed in the rule if "
      + "pre-pruning is used.  If this value is other than -1, then "
      + "pre-pruning will be used, otherwise the rule uses reduced-error "
      + "pruning.";
  }

  /**
   * Sets the number of antecedants
   * 
   * @param n the number of antecedants
   */
  public void setNumAntds(int n) {
    m_NumAntds = n;
  }

  /**
   * Gets the number of antecedants
   * 
   * @return the number of antecedants
   */
  public int getNumAntds() {
    return m_NumAntds;
  }

  /**
   * Returns default capabilities of the classifier.
   * 
   * @return the capabilities of this classifier
   */
  @Override
  public Capabilities getCapabilities() {
    Capabilities result = super.getCapabilities();
    result.disableAll();

    // attributes
    result.enable(Capability.NOMINAL_ATTRIBUTES);
    result.enable(Capability.NUMERIC_ATTRIBUTES);
    result.enable(Capability.DATE_ATTRIBUTES);
    result.enable(Capability.MISSING_VALUES);

    // class
    result.enable(Capability.NOMINAL_CLASS);
    result.enable(Capability.NUMERIC_CLASS);
    result.enable(Capability.DATE_CLASS);
    result.enable(Capability.MISSING_CLASS_VALUES);

    return result;
  }

  /**
   * Builds a single rule learner with REP dealing with nominal classes or
   * numeric classes. For nominal classes, this rule learner predicts a
   * distribution on the classes. For numeric classes, this learner predicts a
   * single value.
   * 
   * @param instances the training data
   * @throws Exception if classifier can't be built successfully
   */
  @Override
  public void buildClassifier(Instances instances) throws Exception {
    // can classifier handle the data?
    getCapabilities().testWithFail(instances);

    // remove instances with missing class
    Instances data = new Instances(instances);
    data.deleteWithMissingClass();

    if (data.numInstances() < m_Folds) {
      throw new Exception("Not enough data for REP.");
    }

    m_ClassAttribute = data.classAttribute();
    if (m_ClassAttribute.isNominal()) {
      m_NumClasses = m_ClassAttribute.numValues();
    } else {
      m_NumClasses = 1;
    }

    m_Antds = new ArrayList<Antd>();
    m_DefDstr = new double[m_NumClasses];
    m_Cnsqt = new double[m_NumClasses];
    m_Targets = new ArrayList<double[][]>();
    m_Random = new Random(m_Seed);

    if (m_NumAntds != -1) {
      grow(data);
    } else {

      data.randomize(m_Random);

      // Split data into Grow and Prune
      data.stratify(m_Folds);

      Instances growData = data.trainCV(m_Folds, m_Folds - 1, m_Random);
      Instances pruneData = data.testCV(m_Folds, m_Folds - 1);

      grow(growData); // Build this rule
      prune(pruneData); // Prune this rule
    }

    if (m_ClassAttribute.isNominal()) {
      Utils.normalize(m_Cnsqt);
      if (Utils.gr(Utils.sum(m_DefDstr), 0)) {
        Utils.normalize(m_DefDstr);
      }
    }
  }

  /**
   * Computes class distribution for the given instance.
   * 
   * @param instance the instance for which distribution is to be computed
   * @return the class distribution for the given instance
   * @throws Exception if given instance is null
   */
  @Override
  public double[] distributionForInstance(Instance instance) throws Exception {
    if (instance == null) {
      throw new Exception("Testing instance is NULL!");
    }

    if (isCover(instance)) {
      return m_Cnsqt;
    } else {
      return m_DefDstr;
    }
  }

  /**
   * Whether the instance covered by this rule
   * 
   * @param datum the instance in question
   * @return the boolean value indicating whether the instance is covered by
   *         this rule
   */
  public boolean isCover(Instance datum) {
    boolean isCover = true;

    for (int i = 0; i < m_Antds.size(); i++) {
      Antd antd = m_Antds.get(i);
      if (!antd.isCover(datum)) {
        isCover = false;
        break;
      }
    }

    return isCover;
  }

  /**
   * Whether this rule has antecedents, i.e. whether it is a default rule
   * 
   * @return the boolean value indicating whether the rule has antecedents
   */
  public boolean hasAntds() {
    if (m_Antds == null) {
      return false;
    } else {
      return (m_Antds.size() > 0);
    }
  }

  /**
   * Build one rule using the growing data
   * 
   * @param data the growing data used to build the rule
   */
  private void grow(Instances data) {
    Instances growData = new Instances(data);
    double defInfo;
    double whole = data.sumOfWeights();

    if (m_NumAntds != 0) {

      /*
       * Class distribution for data both covered and not covered by one
       * antecedent
       */
      double[][] classDstr = new double[2][m_NumClasses];

      /* Compute the default information of the growing data */
      for (int j = 0; j < m_NumClasses; j++) {
        classDstr[0][j] = 0;
        classDstr[1][j] = 0;
      }
      if (m_ClassAttribute.isNominal()) {
        for (int i = 0; i < growData.numInstances(); i++) {
          Instance datum = growData.instance(i);
          classDstr[0][(int) datum.classValue()] += datum.weight();
        }
        defInfo = ContingencyTables.entropy(classDstr[0]);
      } else {
        for (int i = 0; i < growData.numInstances(); i++) {
          Instance datum = growData.instance(i);
          classDstr[0][0] += datum.weight() * datum.classValue();
        }

        // No need to be divided by the denomitor because
        // it's always the same
        double defMean = (classDstr[0][0] / whole);
        defInfo = meanSquaredError(growData, defMean) * growData.sumOfWeights();
      }

      // Store the default class distribution
      double[][] tmp = new double[2][m_NumClasses];
      for (int y = 0; y < m_NumClasses; y++) {
        if (m_ClassAttribute.isNominal()) {
          tmp[0][y] = classDstr[0][y];
          tmp[1][y] = classDstr[1][y];
        } else {
          tmp[0][y] = classDstr[0][y] / whole;
          tmp[1][y] = classDstr[1][y];
        }
      }
      m_Targets.add(tmp);

      /* Keep the record of which attributes have already been used */
      boolean[] used = new boolean[growData.numAttributes()];
      for (int k = 0; k < used.length; k++) {
        used[k] = false;
      }
      int numUnused = used.length;
      double maxInfoGain, uncoveredWtSq = 0, uncoveredWtVl = 0, uncoveredWts = 0;
      boolean isContinue = true; // The stopping criterion of this rule

      while (isContinue) {
        maxInfoGain = 0; // We require that infoGain be positive

        /* Build a list of antecedents */
        Antd oneAntd = null;
        Instances coverData = null, uncoverData = null;
        Enumeration<Attribute> enumAttr = growData.enumerateAttributes();
        int index = -1;

        if (m_Debug) {
          System.out.println("Growing data: " + growData);
        }

        /* Build one condition based on all attributes not used yet */
        while (enumAttr.hasMoreElements()) {
          Attribute att = (enumAttr.nextElement());
          index++;

          Antd antd = null;
          if (m_ClassAttribute.isNominal()) {
            if (att.isNumeric()) {
              antd = new NumericAntd(att, classDstr[1]);
            } else {
              antd = new NominalAntd(att, classDstr[1]);
            }
          } else if (att.isNumeric()) {
            antd = new NumericAntd(att, uncoveredWtSq, uncoveredWtVl,
              uncoveredWts);
          } else {
            antd = new NominalAntd(att, uncoveredWtSq, uncoveredWtVl,
              uncoveredWts);
          }

          if (!used[index]) {

            /*
             * Compute the best information gain for each attribute, it's stored
             * in the antecedent formed by this attribute. This procedure
             * returns the data covered by the antecedent
             */
            Instances[] coveredData = computeInfoGain(growData, defInfo, antd);

            if (coveredData != null) {
              double infoGain = antd.getMaxInfoGain();
              boolean isUpdate = Utils.gr(infoGain, maxInfoGain);

              if (m_Debug) {
                System.err.println(antd);
                System.err.println("Info gain: " + infoGain);
                System.err.println("Max info gain: " + maxInfoGain);
              }

              if (isUpdate) {
                oneAntd = antd;
                coverData = coveredData[0];
                uncoverData = coveredData[1];
                maxInfoGain = infoGain;
              }
            }
          }
        }

        if (oneAntd == null) {
          break;
        }

        if (m_Debug) {
          System.err.println("Adding antecedent: ");
          System.err.println(oneAntd);
          System.err.println("Covered data: ");
          System.err.println(coverData);
          System.err.println("Uncovered data: ");
          System.err.println(uncoverData);
        }

        // Numeric attributes can be used more than once
        if (!oneAntd.getAttr().isNumeric()) {
          used[oneAntd.getAttr().index()] = true;
          numUnused--;
        }

        m_Antds.add(oneAntd);
        growData = coverData;// Grow data size is shrinking

        for (int x = 0; x < uncoverData.numInstances(); x++) {
          Instance datum = uncoverData.instance(x);
          if (m_ClassAttribute.isNumeric()) {
            uncoveredWtSq += datum.weight() * datum.classValue()
              * datum.classValue();
            uncoveredWtVl += datum.weight() * datum.classValue();
            uncoveredWts += datum.weight();
            classDstr[0][0] -= datum.weight() * datum.classValue();
            classDstr[1][0] += datum.weight() * datum.classValue();
          } else {
            classDstr[0][(int) datum.classValue()] -= datum.weight();
            classDstr[1][(int) datum.classValue()] += datum.weight();
          }
        }

        // Store class distribution of growing data
        tmp = new double[2][m_NumClasses];
        for (int y = 0; y < m_NumClasses; y++) {
          if (m_ClassAttribute.isNominal()) {
            tmp[0][y] = classDstr[0][y];
            tmp[1][y] = classDstr[1][y];
          } else {
            tmp[0][y] = classDstr[0][y] / (whole - uncoveredWts);
            tmp[1][y] = classDstr[1][y] / uncoveredWts;
          }
        }
        m_Targets.add(tmp);

        defInfo = oneAntd.getInfo();
        if (m_Debug) {
          System.err.println("Default info: " + defInfo);
        }
        int numAntdsThreshold = (m_NumAntds == -1) ? Integer.MAX_VALUE
          : m_NumAntds;

        if (Utils.eq(growData.sumOfWeights(), 0.0) || (numUnused == 0)
          || (m_Antds.size() >= numAntdsThreshold)) {
          isContinue = false;
        }
      }
    }

    m_Cnsqt = (m_Targets.get(m_Targets.size() - 1))[0];
    m_DefDstr = (m_Targets.get(m_Targets.size() - 1))[1];
  }

  /**
   * Compute the best information gain for the specified antecedent
   * 
   * @param instances the data based on which the infoGain is computed
   * @param defInfo the default information of data
   * @param antd the specific antecedent
   * @return the data covered and not covered by the antecedent
   */
  private Instances[] computeInfoGain(Instances instances, double defInfo,
    Antd antd) {
    Instances data = new Instances(instances);

    /*
     * Split the data into bags. The information gain of each bag is also
     * calculated in this procedure
     */
    Instances[] splitData = antd.splitData(data, defInfo);
    Instances[] coveredData = new Instances[2];

    /* Get the bag of data to be used for next antecedents */
    Instances tmp1 = new Instances(data, 0);
    Instances tmp2 = new Instances(data, 0);

    if (splitData == null) {
      return null;
    }

    for (int x = 0; x < (splitData.length - 1); x++) {
      if (x == ((int) antd.getAttrValue())) {
        tmp1 = splitData[x];
      } else {
        for (int y = 0; y < splitData[x].numInstances(); y++) {
          tmp2.add(splitData[x].instance(y));
        }
      }
    }

    if (antd.getAttr().isNominal()) { // Nominal attributes
      if (((NominalAntd) antd).isIn()) { // Inclusive expression
        coveredData[0] = new Instances(tmp1);
        coveredData[1] = new Instances(tmp2);
      } else { // Exclusive expression
        coveredData[0] = new Instances(tmp2);
        coveredData[1] = new Instances(tmp1);
      }
    } else { // Numeric attributes
      coveredData[0] = new Instances(tmp1);
      coveredData[1] = new Instances(tmp2);
    }

    /* Add data with missing value */
    for (int z = 0; z < splitData[splitData.length - 1].numInstances(); z++) {
      coveredData[1].add(splitData[splitData.length - 1].instance(z));
    }

    return coveredData;
  }

  /**
   * Prune the rule using the pruning data. The weighted average of accuracy
   * rate/mean-squared error is used to prune the rule.
   * 
   * @param pruneData the pruning data used to prune the rule
   */
  private void prune(Instances pruneData) {
    Instances data = new Instances(pruneData);
    Instances otherData = new Instances(data, 0);
    double total = data.sumOfWeights();

    /* The default accurate# and the the accuracy rate on pruning data */
    double defAccu;
    if (m_ClassAttribute.isNumeric()) {
      defAccu = meanSquaredError(pruneData, m_Targets.get(0)[0][0]);
    } else {
      int predict = Utils.maxIndex(m_Targets.get(0)[0]);
      defAccu = computeAccu(pruneData, predict) / total;
    }

    int size = m_Antds.size();
    if (size == 0) {
      m_Cnsqt = m_Targets.get(m_Targets.size() - 1)[0];
      m_DefDstr = m_Targets.get(m_Targets.size() - 1)[1];
      return; // Default rule before pruning
    }

    double[] worthValue = new double[size];

    /* Calculate accuracy parameters for all the antecedents in this rule */
    for (int x = 0; x < size; x++) {
      Antd antd = m_Antds.get(x);
      Instances newData = new Instances(data);
      if (Utils.eq(newData.sumOfWeights(), 0.0)) {
        break;
      }

      data = new Instances(newData, newData.numInstances()); // Make data empty

      for (int y = 0; y < newData.numInstances(); y++) {
        Instance ins = newData.instance(y);
        if (antd.isCover(ins)) {
          data.add(ins); // Add to data for further
        } else {
          otherData.add(ins); // Not covered by this antecedent
        }
      }

      double covered, other;
      double[][] classes = m_Targets.get(x + 1); // m_Targets has one more
                                                 // element
      if (m_ClassAttribute.isNominal()) {
        int coverClass = Utils.maxIndex(classes[0]), otherClass = Utils
          .maxIndex(classes[1]);

        covered = computeAccu(data, coverClass);
        other = computeAccu(otherData, otherClass);
      } else {
        double coverClass = classes[0][0], otherClass = classes[1][0];
        covered = (data.sumOfWeights()) * meanSquaredError(data, coverClass);
        other = (otherData.sumOfWeights())
          * meanSquaredError(otherData, otherClass);
      }

      worthValue[x] = (covered + other) / total;
    }

    /* Prune the antecedents according to the accuracy parameters */
    for (int z = (size - 1); z > 0; z--) {
      // Treatment to avoid precision problems
      double valueDelta;
      if (m_ClassAttribute.isNominal()) {
        if (Utils.sm(worthValue[z], 1.0)) {
          valueDelta = (worthValue[z] - worthValue[z - 1]) / worthValue[z];
        } else {
          valueDelta = worthValue[z] - worthValue[z - 1];
        }
      } else {
        if (Utils.sm(worthValue[z], 1.0)) {
          valueDelta = (worthValue[z - 1] - worthValue[z]) / worthValue[z];
        } else {
          valueDelta = (worthValue[z - 1] - worthValue[z]);
        }
      }

      if (Utils.smOrEq(valueDelta, 0.0)) {
        m_Antds.remove(z);
        m_Targets.remove(z + 1);
      } else {
        break;
      }
    }

    // Check whether this rule is a default rule
    if (m_Antds.size() == 1) {
      double valueDelta;
      if (m_ClassAttribute.isNominal()) {
        if (Utils.sm(worthValue[0], 1.0)) {
          valueDelta = (worthValue[0] - defAccu) / worthValue[0];
        } else {
          valueDelta = (worthValue[0] - defAccu);
        }
      } else {
        if (Utils.sm(worthValue[0], 1.0)) {
          valueDelta = (defAccu - worthValue[0]) / worthValue[0];
        } else {
          valueDelta = (defAccu - worthValue[0]);
        }
      }

      if (Utils.smOrEq(valueDelta, 0.0)) {
        m_Antds.clear();
        m_Targets.remove(1);
      }
    }

    m_Cnsqt = (m_Targets.get(m_Targets.size() - 1))[0];
    m_DefDstr = (m_Targets.get(m_Targets.size() - 1))[1];
  }

  /**
   * Private function to compute number of accurate instances based on the
   * specified predicted class
   * 
   * @param data the data in question
   * @param clas the predicted class
   * @return the default accuracy number
   */
  private double computeAccu(Instances data, int clas) {
    double accu = 0;
    for (int i = 0; i < data.numInstances(); i++) {
      Instance inst = data.instance(i);
      if ((int) inst.classValue() == clas) {
        accu += inst.weight();
      }
    }
    return accu;
  }

  /**
   * Private function to compute the squared error of the specified data and the
   * specified mean
   * 
   * @param data the data in question
   * @param mean the specified mean
   * @return the default mean-squared error
   */
  private double meanSquaredError(Instances data, double mean) {
    if (Utils.eq(data.sumOfWeights(), 0.0)) {
      return 0;
    }

    double mSqErr = 0, sum = data.sumOfWeights();
    for (int i = 0; i < data.numInstances(); i++) {
      Instance datum = data.instance(i);
      mSqErr += datum.weight() * (datum.classValue() - mean)
        * (datum.classValue() - mean);
    }

    return (mSqErr / sum);
  }

  /**
   * Prints this rule with the specified class label
   * 
   * @param att the string standing for attribute in the consequent of this rule
   * @param cl the string standing for value in the consequent of this rule
   * @return a textual description of this rule with the specified class label
   */
  public String toString(String att, String cl) {
    StringBuffer text = new StringBuffer();
    if (m_Antds.size() > 0) {
      for (int j = 0; j < (m_Antds.size() - 1); j++) {
        text.append("(" + (m_Antds.get(j)).toString() + ") and ");
      }
      text.append("(" + (m_Antds.get(m_Antds.size() - 1)).toString() + ")");
    }
    text.append(" => " + att + " = " + cl);

    return text.toString();
  }

  /**
   * Prints this rule
   * 
   * @return a textual description of this rule
   */
  @Override
  public String toString() {
    String title = "\n\nSingle conjunctive rule learner:\n"
      + "--------------------------------\n", body = null;
    StringBuffer text = new StringBuffer();
    if (m_ClassAttribute != null) {
      if (m_ClassAttribute.isNominal()) {
        body = toString(m_ClassAttribute.name(),
          m_ClassAttribute.value(Utils.maxIndex(m_Cnsqt)));

        text.append("\n\nClass distributions:\nCovered by the rule:\n");
        for (int k = 0; k < m_Cnsqt.length; k++) {
          text.append(m_ClassAttribute.value(k) + "\t");
        }
        text.append('\n');
        for (double element : m_Cnsqt) {
          text.append(Utils.doubleToString(element, 6) + "\t");
        }

        text.append("\n\nNot covered by the rule:\n");
        for (int k = 0; k < m_DefDstr.length; k++) {
          text.append(m_ClassAttribute.value(k) + "\t");
        }
        text.append('\n');
        for (double element : m_DefDstr) {
          text.append(Utils.doubleToString(element, 6) + "\t");
        }
      } else {
        body = toString(m_ClassAttribute.name(),
          Utils.doubleToString(m_Cnsqt[0], 6));
      }
    }
    return (title + body + text.toString());
  }

  /**
   * Returns the revision string.
   * 
   * @return the revision
   */
  @Override
  public String getRevision() {
    return RevisionUtils.extract("$Revision$");
  }

  /**
   * Main method.
   * 
   * @param args the options for the classifier
   */
  public static void main(String[] args) {
    runClassifier(new ConjunctiveRule(), args);
  }
}
